package com.griddroid.works;

import java.io.Serializable;
import com.griddroid.computations.Computation;
import com.griddroid.computations.Integral;
import com.griddroid.computations.Product;
import com.griddroid.computations.Sum;
import com.griddroid.exceptions.FoundAlienWorkRequestException;
import com.griddroid.exceptions.NonCoherentComputationException;
import com.griddroid.exceptions.NotCalculatedComputationException;
import com.griddroid.exceptions.UnknownComputationException;
import com.griddroid.serverdaemons.MergerDaemon;

/**
 * Represents the answer to a work request. The worker, after it has processed
 * the work request that the server gave to it, assembles a work response
 * concerning containing the answer to that request. The server is responsible
 * for merging all the responses from the different parts of a work request into
 * one response, and sending it to the appropriate client
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 * 
 * @see WorkRequest
 */
public class WorkResponse implements Serializable
{

	private static final long serialVersionUID = -1244522313123699655L;
	Computation comp;
	String workRequestId;
	private int workFragmentNum;
	private int totalFragments;

	/**
	 * Constructs a work response. It also sets <code>
	 * this.workFragmentNum = 1;
	 * this.totalFragments = 1;
	 * </code> meaning that it is a solid work response (1 out of 1)
	 * 
	 * @param computation
	 *            the actual work of this work request, containing the result
	 *            (i.e. it has been calculated)
	 * @param rqId
	 *            a unique identifier of this work response. It is the id of the
	 *            correspondent work request (same as the id of the client that
	 *            sent the request)
	 */
	public WorkResponse(Computation computation, String rqId)
	{
		this.comp = computation;
		this.workRequestId = rqId;
		this.workFragmentNum = 1;
		this.totalFragments = 1;
	}

	/**
	 * This constructor is used for constructing a response to a part of a
	 * request. That's why we can declare now for which fragment this response
	 * is and how many fragments there are in total
	 * 
	 * @param computation
	 *            the actual work of this work request
	 * @param rqId
	 *            a unique identifier of this work response. It is the id of the
	 *            correspondent work request (same as the id of the client that
	 *            sent the request)
	 * @param frqnum
	 *            the fragment ascending number.
	 * @param total_frqs
	 *            the total number of fragments that the original work request
	 *            was split into.
	 */
	public WorkResponse(Computation computation, String rqId, int frqnum, int total_frqs)
	{
		this.comp = computation;
		this.workRequestId = rqId;
		this.workFragmentNum = frqnum;
		this.totalFragments = total_frqs;
	}

	/**
	 * Merges many work responses (parts) into one. It is used by the
	 * {@link MergerDaemon MergerDaemon}, when it assembles the response to send
	 * back to the client
	 * 
	 * @param wrs an array with responses to merge
	 * @return a single response, that is the combination of the input array
	 * @throws NotCalculatedComputationException one or more responses contain a computation that has no result set
	 * @throws NonCoherentComputationException one or more responses contain a different computation than the rest
	 * @throws UnknownComputationException one or more responses contain a computation that cannot be recognized
	 * @throws FoundAlienWorkRequestException one or more responses in the array belong to a different client
	 */
	public WorkResponse merge(WorkResponse[] wrs) throws NotCalculatedComputationException, NonCoherentComputationException, UnknownComputationException, FoundAlienWorkRequestException
	{
		Computation[] comps = new Computation[wrs.length];

		// Check if all workResponses are from the same workRequest
		String firstWrId = wrs[0].getWorkRequestId();
		for (int i = 0; i < wrs.length; i++)
		{
			if (!wrs[i].getWorkRequestId().equals(firstWrId))
			{
				throw new FoundAlienWorkRequestException();
			}

		}

		int i = 0;
		for (WorkResponse wr : wrs)
		{
			comps[i] = wr.getComputation();
			i++;
		}

		Computation result = null;
		if (comps[0] instanceof Sum)
		{
			result = new Sum();
			result.merge(comps);
		}
		else if (comps[0] instanceof Integral)
		{
			result = new Integral();
			result.merge(comps);
		}
		else if (comps[0] instanceof Product)
		{
			result = new Product();
			result.merge(comps);
		}
		else
			throw new UnknownComputationException();
		return new WorkResponse(result, this.workRequestId);
	}

	public Computation getComputation()
	{
		return this.comp;
	}

	public String getWorkRequestId()
	{
		return workRequestId;
	}

	public String getWorkFragmentId()
	{
		return "" + String.valueOf(this.totalFragments) + "@" + String.valueOf(this.workFragmentNum);
	}

	public int getWorkFragmentNum()
	{
		return workFragmentNum;
	}

	public int getTotalFragments()
	{
		return totalFragments;
	}

	public void setWorkRequestId(String id)
	{
		this.workRequestId = id;
	}

}
